///////////////////////////////////////////////////////////////////////////////
//
// DBT86 Project : Native Process Support : x86/linux, gcc
// Copyright (C) 2004 by Ben Hertzberg and Stanford University
// All Rights Reserved
//
// This code may not be used for any purpose without the express written
// consent of the author.  See the LICENSE.txt file for details.
//
//
// DESCRIPTION
//
// This code enables the environment to emulate at the application level
// instead of running at the system level.
// Note that it requires an apples-to-apples system match (ex: x86/Win32
// on x86/Win32) and system-specific hooks.
//
// This file contains linux-specific implementation
//
///////////////////////////////////////////////////////////////////////////////

#define INCL_INTERP_LOCALS
#include "config.h"
#include "interp.h"
#include "debug.h"

#ifndef LESS86
#include "CPU.h"
#include "arch.h"
#include "base/Simulator.h"
#include "base/isa_ext.h"
#include "base/signals/SigManager.h"
#include "base/simapi.h"
#include "base/syscalls.h"
#endif

#if CFG_NATIVE_PROCESS

#if CFG_NATIVE_PROCESS_FS_INTERCEPT
extern UINT32 SegInterceptBaseFS;
extern UINT32 SegInterceptSizeFS;
extern UINT32 SegInterceptBottomFS;
extern UINT32 SegInterceptLimitFS;
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
extern UINT32 SegInterceptBaseGS;
extern UINT32 SegInterceptSizeGS;
extern UINT32 SegInterceptBottomGS;
extern UINT32 SegInterceptLimitGS;
#endif

// INT 0x80 non-invasive system call support
// TODO(mbarrien): Restore the "static" modifier for both lines. All we need
// is no name mangling using the current assembly, but static forces mangling in GCC4.4
// Right way is to fix the assembly below to use extended ASM
volatile UINT32 savedSP,savedBP, newSP, newBP;
volatile UINT32 reg_eax, reg_ebx, reg_ecx, reg_edx, reg_esi, reg_edi;

void INT_Ib() {
  IVAL id;
  CONTROL_POINT;
  id = LOAD_IMM;
  if (id == 0x0) {
#ifndef LESS86
      int isa_ext = LOAD_REG_EAX;
      Reg arg0 = LOAD_REG_ECX;
      Reg arg1 = LOAD_REG_EDX;
      Reg oldPC = X86_CTXT(IP);
      if (!global_sim->handle_isa_ext(isa_ext, curPE->P, arg0, arg1, &X86_CTXT(GPR[EAX]))) {
          Reg newPC = X86_CTXT(IP);
          // if oldPC == newPC then we are trying to stall so set
          // instrs_executed to false. 
          if (oldPC == newPC) {
              curPE->instrs_executed = false;
          }
          // Otherwise we are just trying to avoid the PPC implicit "PC+=4"
          else {
              X86_TMP(newIP) = newPC;
          }
          return;
      }
#else // !LESS86
      X86_CTXT(IP) += 2; // skip this instruction
      // native execution hook
      DbgNativeExec();
#endif // !LESS86
  } else if (id == 0x80) {
    reg_eax = LOAD_REG_EAX;
    reg_ebx = LOAD_REG_EBX;
    reg_ecx = LOAD_REG_ECX;
    reg_edx = LOAD_REG_EDX;
    reg_esi = LOAD_REG_ESI;
    reg_edi = LOAD_REG_EDI;
    newBP = LOAD_REG_EBP;
    newSP = LOAD_REG_ESP;

    Reg_s sc_id = (Reg_s)reg_eax;
    Reg args[] = {(Reg)reg_ebx, (Reg)reg_ecx, (Reg)reg_edx, (Reg)reg_esi, (Reg)reg_edi, (Reg)newBP};
    Reg* sc_result = &curPE->InterpCtxt.GPR[EAX];
    syscallLog->d("%d:INT_Ib -- trying syscall %d...\n", curPE->getID(), (int)sc_id);

    SysCallHookRet res = do_pre_syscall_hook(curPE, sc_id, args, sc_result);
    if (res == SC_HANDLED) {
        do_post_syscall_hook(curPE, sc_id, args);
        return;
    } else if (res == SC_STALL) {
        curPE->instrs_executed = false;  // TODO: is curPE defined here?
        longjmp(curPE->memoryJumpEnv, 1);
    }      

#if __PIC__
    asm volatile (
      "pushl %%ebx;\n"
      "call .LL2;\n"
      ".LL2:\n"
      "popl %%ebx;\n"
      "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL2],%%ebx;\n"

      "movl reg_ecx@GOT(%%ebx),%%ecx;\n"
      "movl (%%ecx), %%ecx;\n"

      "movl reg_edx@GOT(%%ebx),%%edx;\n"
      "movl (%%edx), %%edx;\n"

      "movl reg_esi@GOT(%%ebx),%%esi;\n"
      "movl (%%esi), %%esi;\n"

      "movl reg_edi@GOT(%%ebx),%%edi;\n"
      "movl (%%edi), %%edi;\n"

      "movl savedSP@GOT(%%ebx),%%eax;\n"
      "movl %%esp, (%%eax);\n"

      "movl newSP@GOT(%%ebx),%%eax;\n"
      "movl (%%eax), %%esp;\n"

      "movl savedBP@GOT(%%ebx),%%eax;\n"
      "movl %%ebp, (%%eax);\n"

      "movl newBP@GOT(%%ebx),%%eax;\n"
      "movl (%%eax), %%ebp;\n"

      "movl reg_eax@GOT(%%ebx),%%eax;\n"
      "movl (%%eax), %%eax;\n"

      "movl reg_ebx@GOT(%%ebx),%%ebx;\n"
      "movl (%%ebx), %%ebx;\n"

      "int $0x80;\n"

      "pushl %%ebx;\n"
      "pushl %%eax;\n"

      "call .LL3;\n"
      ".LL3:\n"
      "popl %%ebx;\n"
      "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL3],%%ebx;\n"

      "movl reg_edi@GOT(%%ebx),%%eax;\n"
      "movl %%edi, (%%eax);\n"

      "movl reg_esi@GOT(%%ebx),%%eax;\n"
      "movl %%esi, (%%eax);\n"

      "movl reg_edx@GOT(%%ebx),%%eax;\n"
      "movl %%edx, (%%eax);\n"

      "movl reg_ecx@GOT(%%ebx),%%eax;\n"
      "movl %%ecx, (%%eax);\n"

      "popl %%edx;\n"
      "movl reg_eax@GOT(%%ebx),%%eax;\n"
      "movl %%edx, (%%eax);\n"

      "popl %%edx;\n"
      "movl reg_ebx@GOT(%%ebx),%%eax;\n"
      "movl %%edx, (%%eax);\n"

      "movl newBP@GOT(%%ebx),%%eax;\n"
      "movl %%ebp, (%%eax);\n"

      "movl savedBP@GOT(%%ebx),%%eax;\n"
      "movl (%%eax), %%ebp;\n"

      "movl newSP@GOT(%%ebx),%%eax;\n"
      "movl %%esp, (%%eax);\n"

      "movl savedSP@GOT(%%ebx),%%eax;\n"
      "movl (%%eax), %%esp;\n"

      "popl %%ebx;\n"
      :
      :
      : "eax","ecx","edx","esi","edi","memory"
    );
#else
    asm volatile (
      "movl reg_eax, %%eax;\n"
      "movl reg_ebx, %%ebx;\n"
      "movl reg_ecx, %%ecx;\n"
      "movl reg_edx, %%edx;\n"
      "movl reg_esi, %%esi;\n"
      "movl reg_edi, %%edi;\n"
      "movl %%esp, savedSP;\n"
      "movl newSP, %%esp;\n"
      "movl %%ebp, savedBP;\n"
      "movl newBP, %%ebp;\n"
      "int $0x80;\n"
      "movl %%ebp, newBP;\n"
      "movl savedBP, %%ebp;\n"
      "movl %%esp, newSP;\n"
      "movl savedSP, %%esp;\n"
      "movl %%edi, reg_edi;\n"
      "movl %%esi, reg_esi;\n"
      "movl %%edx, reg_edx;\n"
      "movl %%ecx, reg_ecx;\n"
      "movl %%ebx, reg_ebx;\n"
      "movl %%eax, reg_eax;\n"
      :
      :
      : "eax", "ebx", "ecx","edx","esi","edi","memory"
    );
#endif
    STORE_REG_ID_DWORD(EAX,reg_eax);
    STORE_REG_ID_DWORD(EBX,reg_ebx);
    STORE_REG_ID_DWORD(ECX,reg_ecx);
    STORE_REG_ID_DWORD(EDX,reg_edx);
    STORE_REG_ID_DWORD(ESI,reg_esi);
    STORE_REG_ID_DWORD(EDI,reg_edi);
    STORE_REG_ID_DWORD(EBP,newBP);
    STORE_REG_ID_DWORD(ESP,newSP);

    do_post_syscall_hook(curPE, sc_id, args);
  } else {
      curPE->P->getTrapGenerator()->generateTrap();      
  }
}

// SYSENTER for non-intrusive system call interception
// NOTE: Expected Behavior of Linux SYSENTER
//
// On Linux, the SYSENTER instruction returns to a fixed sequence:
//   pop ebp
//   pop edx
//   pop ecx
//   ret
//
// We don't have control of this return point, so we need to intercept
// as soon as possible, at the "ret" instruction.  Replace the return
// IP with our own, saving off the old value.  When we resume execution,
// the pops will already have been executed, so just resume at the
// program's desired return address.
// TODO(mbarrien): Restore the "static" modifier
volatile UINT32 savedRA;
void SYSENTER() {

  // NOTE: at some point (possibly older gcc?) was having trouble with the "call +0" sequence
  CONTROL_POINT;
  reg_eax = LOAD_REG_EAX;
  reg_ebx = LOAD_REG_EBX;
  reg_ecx = LOAD_REG_ECX;
  reg_edx = LOAD_REG_EDX;
  reg_esi = LOAD_REG_ESI;
  reg_edi = LOAD_REG_EDI;
  newBP = LOAD_REG_EBP;
  newSP = LOAD_REG_ESP;

  Reg_s sc_id = (Reg_s)reg_eax;
  Reg args[] = {(Reg)reg_ebx, (Reg)reg_ecx, (Reg)reg_edx, (Reg)reg_esi, (Reg)reg_edi, (Reg)newBP};
  Reg* sc_result = &curPE->InterpCtxt.GPR[EAX];
  syscallLog->d("%d:SYSENTER -- trying syscall %d...\n", curPE->getID(), (int)sc_id);

  SysCallHookRet res = do_pre_syscall_hook(curPE, sc_id, args, sc_result);

  if (res == SC_STALL) {
      curPE->instrs_executed = false; 
      longjmp(curPE->memoryJumpEnv, 1);
  }      
  
  // need to rawFlushCaches to read savedRA
  global_sim->rawFlushCaches(newSP, 16);
  // need to invalidate so that updates to SYSENTER frame are 
  global_sim->invalidateCaches(curPE->P, __func__, newSP, 16);

  savedRA = *ADDR_UINT32STAR(newSP+12); // Linux SYSENTER frame supposedly looks like this...

  if (res == SC_HANDLED) {
      do_post_syscall_hook(curPE, sc_id, args);
      // mimic what the caller of sysenter expected linux to do.
      STORE_REG_ID_DWORD(EBP,*ADDR_UINT32STAR(newSP))
      STORE_REG_ID_DWORD(EDX,*ADDR_UINT32STAR(newSP+4))
      STORE_REG_ID_DWORD(ECX,*ADDR_UINT32STAR(newSP+8))
      STORE_REG_ID_DWORD(ESP,(newSP+16));
      X86_TMP(newIP) = savedRA;
      return;
  }

#if __PIC__
  // NOTE: removed "ebx" from clobber list to avoid position-independent-code error
  asm volatile (
    "pushl %%ebx;\n"
    "call .LL4;\n"
    ".LL4:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL4],%%ebx;\n"

    "movl reg_ecx@GOT(%%ebx),%%ecx;\n"
    "movl (%%ecx), %%ecx;\n"

    "movl reg_edx@GOT(%%ebx),%%edx;\n"
    "movl (%%edx), %%edx;\n"

    "movl reg_esi@GOT(%%ebx),%%esi;\n"
    "movl (%%esi), %%esi;\n"

    "movl reg_edi@GOT(%%ebx),%%edi;\n"
    "movl (%%edi), %%edi;\n"

    "movl savedSP@GOT(%%ebx),%%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl newSP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%esp;\n"

    "movl savedBP@GOT(%%ebx),%%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl newBP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%ebp;\n"

    ".globl MY_SYSENTER_RETURN\n"
    "pushl $MY_SYSENTER_RETURN;\n"
    "popl %%eax;\n"
    "movl %%eax, 12(%%esp);\n"

    "movl reg_eax@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%eax;\n"

    "movl reg_ebx@GOT(%%ebx),%%ebx;\n"
    "movl (%%ebx), %%ebx;\n"

    "sysenter;\n"

    "MY_SYSENTER_RETURN:\n"

    "pushl %%ebx;\n"
    "pushl %%eax;\n"

    "call .LL5;\n"
    ".LL5:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL5],%%ebx;\n"

    "movl reg_edi@GOT(%%ebx),%%eax;\n"
    "movl %%edi, (%%eax);\n"

    "movl reg_esi@GOT(%%ebx),%%eax;\n"
    "movl %%esi, (%%eax);\n"

    "movl reg_edx@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "movl reg_ecx@GOT(%%ebx),%%eax;\n"
    "movl %%ecx, (%%eax);\n"

    "popl %%edx;\n"
    "movl reg_eax@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "popl %%edx;\n"
    "movl reg_ebx@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "movl newBP@GOT(%%ebx),%%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl savedBP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%ebp;\n"

    "movl newSP@GOT(%%ebx),%%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl savedSP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%esp;\n"

    "popl %%ebx;\n"
    :
    :
    : "eax","ecx","edx","esi","edi","memory"
  );
#else
  asm volatile (
    "movl reg_ebx, %%ebx;\n"
    "movl reg_ecx, %%ecx;\n"
    "movl reg_edx, %%edx;\n"
    "movl reg_esi, %%esi;\n"
    "movl reg_edi, %%edi;\n"
    "movl %%esp, savedSP;\n"
    "movl newSP, %%esp;\n"
    "movl %%ebp, savedBP;\n"
    "movl newBP, %%ebp;\n"
    ".globl MY_SYSENTER_RETURN\n"
    "pushl $MY_SYSENTER_RETURN;\n"
    "popl %%eax;\n"
    "movl %%eax, 12(%%esp);\n"
    "movl reg_eax, %%eax;\n"
    "sysenter;\n"
    "MY_SYSENTER_RETURN:\n"
    "movl %%ebp, newBP;\n"
    "movl savedBP, %%ebp;\n"
    "movl %%esp, newSP;\n"
    "movl savedSP, %%esp;\n"
    "movl %%edi, reg_edi;\n"
    "movl %%esi, reg_esi;\n"
    "movl %%edx, reg_edx;\n"
    "movl %%ecx, reg_ecx;\n"
    "movl %%ebx, reg_ebx;\n"
    "movl %%eax, reg_eax;\n"
    :
    :
    : "eax","ecx", "edx","ebx","esi","edi","memory"
  );
#endif
  STORE_REG_ID_DWORD(EAX,reg_eax);
  STORE_REG_ID_DWORD(EBX,reg_ebx);
  STORE_REG_ID_DWORD(ECX,reg_ecx);
  STORE_REG_ID_DWORD(EDX,reg_edx);
  STORE_REG_ID_DWORD(ESI,reg_esi);
  STORE_REG_ID_DWORD(EDI,reg_edi);
  STORE_REG_ID_DWORD(EBP,newBP);
  STORE_REG_ID_DWORD(ESP,newSP);
  X86_TMP(newIP) = savedRA;

  do_post_syscall_hook(curPE, sc_id, args);
}

#if CFG_AMDEXT
void SYSCALL() {
  // NOTE: at some point (possibly older gcc?) was having trouble with the "call +0" sequence
  CONTROL_POINT;
  reg_eax = LOAD_REG_EAX;
  reg_ebx = LOAD_REG_EBX;
  reg_ecx = LOAD_REG_ECX;
  reg_edx = LOAD_REG_EDX;
  reg_esi = LOAD_REG_ESI;
  reg_edi = LOAD_REG_EDI;
  newBP = LOAD_REG_EBP;
  newSP = LOAD_REG_ESP;


  Reg_s sc_id = (Reg_s)reg_eax;
  Reg args[] = {(Reg)reg_ebx, (Reg)reg_ecx, (Reg)reg_edx, (Reg)reg_esi, (Reg)reg_edi, (Reg)newBP};
  Reg* sc_result = &curPE->InterpCtxt.GPR[EAX];
  syscallLog->d("%d:SYSCALL -- trying syscall %d...\n", curPE->getID(), (int)sc_id);

  SysCallHookRet res = do_pre_syscall_hook(curPE, sc_id, args, sc_result);
  if (res == SC_HANDLED) {
      do_post_syscall_hook(curPE, sc_id, args);
      return;
  } else if (res == SC_STALL) {
      curPE->instrs_executed = false;  // TODO: is curPE defined here?
      longjmp(curPE->memoryJumpEnv, 1);
  }

#if __PIC__
  // NOTE: removed "ebx" from clobber list to avoid position-independent-code error
  asm volatile (
    "pushl %%ebx;\n"
    "call .LL11;\n"
    ".LL11:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL11],%%ebx;\n"

    "movl reg_ecx@GOT(%%ebx),%%ecx;\n"
    "movl (%%ecx), %%ecx;\n"

    "movl reg_edx@GOT(%%ebx),%%edx;\n"
    "movl (%%edx), %%edx;\n"

    "movl reg_esi@GOT(%%ebx),%%esi;\n"
    "movl (%%esi), %%esi;\n"

    "movl reg_edi@GOT(%%ebx),%%edi;\n"
    "movl (%%edi), %%edi;\n"

    "movl savedSP@GOT(%%ebx),%%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl newSP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%esp;\n"

    "movl savedBP@GOT(%%ebx),%%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl newBP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%ebp;\n"

    "movl reg_eax@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%eax;\n"

    "movl reg_ebx@GOT(%%ebx),%%ebx;\n"
    "movl (%%ebx), %%ebx;\n"

    "syscall;\n"

    "pushl %%ebx;\n"
    "pushl %%eax;\n"

    "call .LL12;\n"
    ".LL12:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL12],%%ebx;\n"

    "movl reg_edi@GOT(%%ebx),%%eax;\n"
    "movl %%edi, (%%eax);\n"

    "movl reg_esi@GOT(%%ebx),%%eax;\n"
    "movl %%esi, (%%eax);\n"

    "movl reg_edx@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "movl reg_ecx@GOT(%%ebx),%%eax;\n"
    "movl %%ecx, (%%eax);\n"

    "popl %%edx;\n"
    "movl reg_eax@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "popl %%edx;\n"
    "movl reg_ebx@GOT(%%ebx),%%eax;\n"
    "movl %%edx, (%%eax);\n"

    "movl newBP@GOT(%%ebx),%%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl savedBP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%ebp;\n"

    "movl newSP@GOT(%%ebx),%%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl savedSP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%esp;\n"

    "popl %%ebx;\n"
    :
    :
    : "eax","ecx","edx","esi","edi","memory"
  );
#else
  asm volatile (
    "movl reg_ebx, %%ebx;\n"
    "movl reg_ecx, %%ecx;\n"
    "movl reg_edx, %%edx;\n"
    "movl reg_esi, %%esi;\n"
    "movl reg_edi, %%edi;\n"
    "movl %%esp, savedSP;\n"
    "movl newSP, %%esp;\n"
    "movl %%ebp, savedBP;\n"
    "movl newBP, %%ebp;\n"
    "movl reg_eax, %%eax;\n"
    "syscall;\n"
    "movl %%ebp, newBP;\n"
    "movl savedBP, %%ebp;\n"
    "movl %%esp, newSP;\n"
    "movl savedSP, %%esp;\n"
    "movl %%edi, reg_edi;\n"
    "movl %%esi, reg_esi;\n"
    "movl %%edx, reg_edx;\n"
    "movl %%ecx, reg_ecx;\n"
    "movl %%ebx, reg_ebx;\n"
    "movl %%eax, reg_eax;\n"
    :
    :
    : "eax","ecx","edx","ebx","esi","edi","memory"
  );
#endif
  STORE_REG_ID_DWORD(EAX,reg_eax);
  STORE_REG_ID_DWORD(EBX,reg_ebx);
  STORE_REG_ID_DWORD(ECX,reg_ecx);
  STORE_REG_ID_DWORD(EDX,reg_edx);
  STORE_REG_ID_DWORD(ESI,reg_esi);
  STORE_REG_ID_DWORD(EDI,reg_edi);
  STORE_REG_ID_DWORD(EBP,newBP);
  STORE_REG_ID_DWORD(ESP,newSP);

  do_post_syscall_hook(curPE, sc_id, args);
}
#endif

// DbgNativeExec - transfer all virtual state to real state, continue execution
// NOTE: gcc was doing buggy things when fenv_buf was a local (fldenv and fstenv not getting same argument address)
// TODO(mbarrien): Restore the "static" modifier
volatile INT32 flags_mask, reg_flags, fenv_buf_addr;
volatile UINT8 fenv_buf[28];
void DbgNativeExec() {
#if CFG_FPU
  // gcc does some weird stuff here, must be my fault, but finally works as-is (don't ask)
  // 1) it will delete fenv_buf unless there are explicit references to it (despite it being volatile, etc.)
  // 2) it was producing different addresses for different fenv_buf references when using it as
  //    an argument directly, so instead we save it off in a variable, and finally things start
  //    to look more sane
  fenv_buf_addr = (INT)&fenv_buf;
  for (INT i = sizeof(fenv_buf) - 1; i >= 0; --i) {
    fenv_buf[i] = 0;
  }

  // mask off reserved bits in FPcontrol
  *(volatile short *)(fenv_buf+0) = X86_CTXT(FPcontrol) & 0x00001F3F; 

  // FPstatus in the simulator doesn't include FPtop, but it doesn't matter
  // since we transfer registers solely by clearing the tag bits and then pushing
  // FPstatus and FPtop are separate in the simulator, merge them here 
  *(volatile short *)(fenv_buf+4) = X86_CTXT(FPstatus) & 0x0000C7FF;

  // Tag bits 11 indicate that a register is empty.  These will be marked
  // valid when we push in the non-empty registers.
  *(volatile short *)(fenv_buf+8) = 0x0000FFFF;

  // The rest of the stuff in the buffer is for state transfer to exception 
  // handlers, we just leave it zeros.

#if __PIC__
  asm volatile (
    "pushl %%ebx;\n"
    "call .LL6;\n"
    ".LL6:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL6],%%ebx;\n"

    "movl fenv_buf_addr@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%eax;\n"
    "fldenv (%%eax);\n"
    "popl %%ebx;\n"
    : : : "eax"
    );
#else
  asm volatile ("movl fenv_buf_addr, %%eax;\n fldenv (%%eax);\n" : : : "eax");
#endif // __PIC__

  // Find the reg deepest into the stack that is not empty
  volatile INT i = 7;
  while (i >= 0 && !FpuGetReg(i, (char*)fenv_buf)) {
    --i;
  }
  while (i >= 0) {
#if __PIC__
    asm volatile (
        "pushl %%ebx;\n"
        "call .LL7;\n"
        ".LL7:\n"
        "popl %%ebx;\n"
        "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL7],%%ebx;\n"

        "movl fenv_buf_addr@GOT(%%ebx),%%eax;\n"
        "movl (%%eax), %%eax;\n"
        "fldt (%%eax);\n"
        "popl %%ebx;\n"
        : : : "eax"
        );
#else
      asm volatile ("movl fenv_buf_addr, %%eax;\n fldt (%%eax);\n" : : : "eax");
#endif  // __PIC__
    --i;
    if (i >= 0) {
      FpuGetReg(i, (char*)fenv_buf);
    }
  }

#endif // CFG_FPU

  // set int regs and flags, then transfer
  reg_eax    = X86_CTXT(GPR[EAX]);
  reg_ecx    = X86_CTXT(GPR[ECX]);
  reg_edx    = X86_CTXT(GPR[EDX]);
  reg_ebx    = X86_CTXT(GPR[EBX]);
  newSP      = X86_CTXT(GPR[ESP]);
  newBP      = X86_CTXT(GPR[EBP]);
  reg_esi    = X86_CTXT(GPR[ESI]);
  reg_edi    = X86_CTXT(GPR[EDI]);
  reg_flags  = getFlags();
  savedRA    = X86_CTXT(IP);
  flags_mask = ~(EFLAGS_BIT_O | EFLAGS_BIT_D | EFLAGS_BIT_S | EFLAGS_BIT_Z | EFLAGS_BIT_A | EFLAGS_BIT_P | EFLAGS_BIT_C);
  savedSP = savedSP;
  savedBP = savedBP;

#if __PIC__
  // NOTE: removed "ebx" from clobber list to avoid position-independent-code error
  asm volatile (
    "pushl %%ebx;\n"
    "call .LL9;\n"
    ".LL9:\n"
    "popl %%ebx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL9],%%ebx;\n"

    "movl flags_mask@GOT(%%ebx),%%eax;\n"
    "pushfl;\n"
    "popl %%edx;\n"
    "andl (%%eax), %%edx;\n"
    "movl reg_flags@GOT(%%ebx),%%eax;\n"
    "orl (%%eax), %%edx;\n"
    "pushl %%edx;\n"
    "popfl;\n"

    "movl reg_ecx@GOT(%%ebx),%%ecx;\n"
    "movl (%%ecx), %%ecx;\n"

    "movl reg_edx@GOT(%%ebx),%%edx;\n"
    "movl (%%edx), %%edx;\n"

    "movl reg_esi@GOT(%%ebx),%%esi;\n"
    "movl (%%esi), %%esi;\n"

    "movl reg_edi@GOT(%%ebx),%%edi;\n"
    "movl (%%edi), %%edi;\n"

    "movl savedSP@GOT(%%ebx),%%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl newSP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%esp;\n"

    "movl savedBP@GOT(%%ebx),%%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl newBP@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%ebp;\n"

    "movl savedRA@GOT(%%ebx),%%eax;\n"
    "pushl (%%eax);\n"

    "movl reg_eax@GOT(%%ebx),%%eax;\n"
    "movl (%%eax), %%eax;\n"

    "movl reg_ebx@GOT(%%ebx),%%ebx;\n"
    "movl (%%ebx), %%ebx;\n"

    "ret;\n"

    ".globl MY_RESUME_FROM_REAL_LABEL;\n"
    "MY_RESUME_FROM_REAL_LABEL:\n"

    "call .LL10;\n"
    ".LL10:\n"
    "popl %%edx;\n"
    "addl $_GLOBAL_OFFSET_TABLE_+[.- .LL10],%%edx;\n"

    "movl savedRA@GOT(%%edx), %%eax;\n"
    "movl (%%esp), %%ecx;\n"
    "movl %%ecx, (%%eax);\n"

    "movl newBP@GOT(%%edx), %%eax;\n"
    "movl %%ebp, (%%eax);\n"

    "movl savedBP@GOT(%%edx), %%eax;\n"
    "movl (%%eax), %%ebp;\n"

    "movl newSP@GOT(%%edx), %%eax;\n"
    "movl %%esp, (%%eax);\n"

    "movl savedSP@GOT(%%edx), %%eax;\n"
    "movl (%%eax), %%esp;\n"

    "movl reg_edi@GOT(%%edx), %%eax;\n"
    "movl %%edi, (%%eax);\n"

    "movl reg_esi@GOT(%%edx), %%eax;\n"
    "movl %%esi, (%%eax);\n"

    "movl reg_ebx@GOT(%%edx), %%eax;\n"
    "movl %%ebx, (%%eax);\n"

    "popl %%ebx;\n"
      :
      :
      : "eax","ecx","edx","esi","edi","memory"
  );
  reg_eax = 0;
  reg_ecx = 0;
  reg_edx = 0;
#else
  asm volatile (
    "pushfl;\n"
    "popl %%eax;\n"
    "andl flags_mask, %%eax;\n"
    "orl reg_flags, %%eax;\n"
    "pushl %%eax;\n"
    "popfl;\n"
    "movl reg_eax, %%eax;\n"
    "movl reg_ebx, %%ebx;\n"
    "movl reg_ecx, %%ecx;\n"
    "movl reg_edx, %%edx;\n"
    "movl reg_esi, %%esi;\n"
    "movl reg_edi, %%edi;\n"
    "movl %%esp, savedSP;\n"
    "movl newSP, %%esp;\n"
    "movl %%ebp, savedBP;\n"
    "movl newBP, %%ebp;\n"
    "jmp *savedRA;\n"
    ".globl MY_RESUME_FROM_REAL_LABEL;\n"
    "MY_RESUME_FROM_REAL_LABEL:\n"
    "movl 4(%%ebp), %%eax;\n"
    "movl %%eax, savedRA;\n"
    "movl %%ebp, newBP;\n"
    "movl savedBP, %%ebp;\n"
    "movl %%esp, newSP;\n"
    "movl savedSP, %%esp;\n"
    "movl %%edi, reg_edi;\n"
    "movl %%esi, reg_esi;\n"
    "movl %%edx, reg_edx;\n"
    "movl %%ecx, reg_ecx;\n"
    "movl %%ebx, reg_ebx;\n"
    "movl %%eax, reg_eax;\n"
      :
      :
      : "eax", "ecx", "edx","ebx","esi","edi","memory"
  );
  reg_eax = 0; 
#endif // __PIC__
  // NOTE: we clobber eax upon resume, but that's ok for the same reason we don't
  //       have to care about flags either (caller-saved)
  STORE_REG_ID_DWORD(EAX,reg_eax);
  STORE_REG_ID_DWORD(EBX,reg_ebx);
  STORE_REG_ID_DWORD(ECX,reg_ecx);
  STORE_REG_ID_DWORD(EDX,reg_edx);
  STORE_REG_ID_DWORD(ESI,reg_esi);
  STORE_REG_ID_DWORD(EDI,reg_edi);
  // we're resuming simulation at the return address of the caller
  STORE_REG_ID_DWORD(EBP,newBP);
  STORE_REG_ID_DWORD(ESP,(newSP+4)); // skip saved RA
  X86_TMP(newIP) = savedRA;

  global_sim->getSM()->checkHandlerRegistration();
  global_sim->getTM()->goto_sim();
  
  ASSERT(!inSimulation);
  inSimulation = true;

}

// memory interface
UINT8 LoadB (IVAL va, bool uncached) {
  UINT32 addr;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < SegInterceptSizeFS) {
    UINT8 res;
    addr -= SegInterceptBaseFS;
    asm volatile ( "movb %%fs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else 
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < SegInterceptSizeGS) {
    UINT8 res;
    addr -= SegInterceptBaseGS;
    asm volatile ( "movb %%gs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else
#endif
  {
      if (uncached) {
          return *((UINT8*)addr);
      } else {
          UINT64 data64;
          if (curPE->MemoryRead(&data64, (Reg)addr, 1, 0) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          if ((UINT8)data64 != *((UINT8*)addr)) {
              Log->d("addr: %x:%llx %x %x\n", addr, data64, (UINT8)data64, *((UINT8*)addr));
              ASSERT((UINT8)data64 == *((UINT8*)addr));
          }
#endif
          return (UINT8)data64;
      }
  } 
}

UINT16 LoadH (IVAL va, bool uncached) {
  UINT32 addr;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-1)) {
    UINT16 res;
    addr -= SegInterceptBaseFS;
    asm volatile ( "movw %%fs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else 
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-1)) {
    UINT16 res;
    addr -= SegInterceptBaseGS;
    asm volatile ( "movw %%gs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else
#endif
  {
      if (uncached) {
          return *((UINT16*)addr);
      } else {
          UINT64 data64;
          if (curPE->MemoryRead(&data64, (Reg)addr, 2, 0) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT((UINT16)data64 == *((UINT16*)addr));
#endif
          return (UINT16)data64;
      }
  } 
}

UINT32 LoadW(IVAL va, bool uncached) {
  UINT32 addr;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-3)) {
    UINT32 res;
    addr -= SegInterceptBaseFS;
    asm volatile ( "movl %%fs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-3)) {
    UINT32 res;
    addr -= SegInterceptBaseGS;
    asm volatile ( "movl %%gs:(%1), %0;\n" : "=r" (res) : "r" (addr) : "memory");
    return res;
  } else
#endif
  {
      if (uncached) {
          return *((UINT32*)addr);
      } else {
          // TODO: austenmc: is this a signed load?
          UINT64 data64;
          if (curPE->MemoryRead(&data64, (Reg)addr, 4, 0) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT((UINT32)data64 == *((UINT32*)addr));
#endif
          return (UINT32)data64;
      }
  } 
}

UINT64 LoadD(IVAL va, bool uncached) {
  UINT32 addr;
  UINT64 data;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-7)) {
    UINT32 resl,resh;
    addr -= SegInterceptBaseFS;
    asm volatile ( "movl %%fs:(%2), %0;\n movl %%fs:4(%2), %1;\n" : "=&r" (resl), "=&r" (resh) : "r" (addr) : "memory");
    return (((UINT64)resh) << 32) | (UINT64)resl;
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-7)) {
    UINT32 resl,resh;
    addr -= SegInterceptBaseGS;
    asm volatile ( "movl %%gs:(%2), %0;\n movl %%gs:4(%2), %1;\n" : "=&r" (resl), "=&r" (resh) : "r" (addr) : "memory");
    return (((UINT64)resh) << 32) | (UINT64)resl;
  } else
#endif
  {
      if (uncached) {
          return *((UINT64*)addr);
      } else {
          if (curPE->MemoryRead(&data, (Reg)addr, 8, 0) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT(data == *((UINT64*)addr));
#endif
          return data;
      }
  }
}

void StoreB(IVAL val, IVAL va, bool uncached) {
  UINT8 data;
  UINT32 addr;
  data = (UINT8)val;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < SegInterceptSizeFS) {
    addr -= SegInterceptBaseFS;
    asm volatile ( "movb %0, %%fs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < SegInterceptSizeGS) {
    addr -= SegInterceptBaseGS;
    asm volatile ( "movb %0, %%gs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
  {
      if (uncached) {
          *((UINT8*)addr) = data;
      } else {
          UINT64 data64 = (UINT64)data;
          if (curPE->MemoryWrite(data64, (Reg)addr, 1) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT(data == *((UINT8*)addr));
#endif
      }
  } 
}

void StoreH(IVAL val, IVAL va, bool uncached) {
  UINT16 data;
  UINT32 addr;
  data = (UINT16)val;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-1)) {
    addr -= SegInterceptBaseFS;
    asm volatile ( "movw %0, %%fs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-1)) {
    addr -= SegInterceptBaseGS;
    asm volatile ( "movw %0, %%gs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
  {
      if (uncached) {
          *((UINT16*)addr) = data;
      } else {
          UINT64 data64 = (UINT64)data;
          if (curPE->MemoryWrite(data64, (Reg)addr, 2) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT(data == *((UINT16*)addr));
#endif
      }
  } 
}

void StoreW(IVAL val, IVAL va, bool uncached) {
  UINT32 data;
  UINT32 addr;
  data = (UINT32)val;
  addr = (UINT32)va;
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-3)) {
    addr -= SegInterceptBaseFS;
    asm volatile ( "movl %0, %%fs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-3)) {
    addr -= SegInterceptBaseGS;
    asm volatile ( "movl %0, %%gs:(%1);\n" : : "Q" (data), "r" (addr) : "memory");
  } else
#endif
  {
      if (uncached) {
          *((UINT32*)addr) = data;
      } else {
          UINT64 data64 = (UINT64)data;
          if (curPE->MemoryWrite(data64, (Reg)addr, 4) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT(data == *((UINT32*)addr));
#endif
      }
  } 
}

void StoreD(IVAL val, IVAL va, bool uncached) {
  UINT32 datal,datah;
  UINT32 addr;

  addr = (UINT32)va;
  datal = (UINT32)val;
  datah = (UINT32)(val >> 32);
#if CFG_NATIVE_PROCESS_FS_INTERCEPT
  if ((addr - SegInterceptBottomFS) < (SegInterceptSizeFS-7)) {
    addr -= SegInterceptBaseFS;
    asm volatile ( "movl %0, %%fs:(%2);\n movl %1, %%fs:4(%2);\n" : : "r" (datal), "r" (datah), "r" (addr) : "memory");
  } else
#endif
#if CFG_NATIVE_PROCESS_GS_INTERCEPT
  if ((addr - SegInterceptBottomGS) < (SegInterceptSizeGS-7)) {
    addr -= SegInterceptBaseGS;
    asm volatile ( "movl %0, %%gs:(%2);\n movl %1, %%gs:4(%2);\n" : : "r" (datal), "r" (datah), "r" (addr) : "memory");
  } else
#endif
  {
      if (uncached) {
          *((UINT32*)addr) = datal;
          *(((UINT32*)addr) + 1) = datah;
      } else {
          UINT64 data64 = (UINT64)val;
          if (curPE->MemoryWrite(data64, (Reg)addr, 8) != success) {
              curPE->instrs_executed = false;
              longjmp(curPE->memoryJumpEnv, 1);
          }
#if 0
          ASSERT(datal == *((UINT32*)addr));
          ASSERT(datah == *(((UINT32*)addr) + 1));
#endif
      }
  } 
}

//
// atomic ops - pain in the rear to simulate faithfully
//

void LockInvalid() { ASSERT(UNREACHED); }

// ADD
void LockAddB() { ASSERT(UNREACHED); }
void LockAddH() { ASSERT(UNREACHED); }
void LockAddW() { ASSERT(UNREACHED); }

// ADC
void LockAdcB() { ASSERT(UNREACHED); }
void LockAdcH() { ASSERT(UNREACHED); }
void LockAdcW() { ASSERT(UNREACHED); }

// AND
void LockAndB() { ASSERT(UNREACHED); }
void LockAndH() { ASSERT(UNREACHED); }
void LockAndW() { ASSERT(UNREACHED); }

// BTC
void LockBtcH() { ASSERT(UNREACHED); }
void LockBtcW() { ASSERT(UNREACHED); }

// BTR
void LockBtrH() { ASSERT(UNREACHED); }
void LockBtrW() { ASSERT(UNREACHED); }

// BTS
void LockBtsH() { ASSERT(UNREACHED); }
void LockBtsW() { ASSERT(UNREACHED); }

// CMPXCHG, CMPXCHG8B

void LockCmpXchgB() { ASSERT(UNREACHED); }
void LockCmpXchgH() { ASSERT(UNREACHED); }
void LockCmpXchgW() { ASSERT(UNREACHED); }
void LockCmpXchgD() { ASSERT(UNREACHED); }

// DEC
void LockDecB() { ASSERT(UNREACHED); }
void LockDecH() { ASSERT(UNREACHED); }
void LockDecW() { ASSERT(UNREACHED); }

// INC
void LockIncB() { ASSERT(UNREACHED); }
void LockIncH() { ASSERT(UNREACHED); }
void LockIncW() { ASSERT(UNREACHED); }

// NEG
void LockNegB() { ASSERT(UNREACHED); }
void LockNegH() { ASSERT(UNREACHED); }
void LockNegW() { ASSERT(UNREACHED); }

// NOT
void LockNotB() { ASSERT(UNREACHED); }
void LockNotH() { ASSERT(UNREACHED); }
void LockNotW() { ASSERT(UNREACHED); }

// OR
void LockOrB() { ASSERT(UNREACHED); }
void LockOrH() { ASSERT(UNREACHED); }
void LockOrW() { ASSERT(UNREACHED); }

// SBB
void LockSbbB() { ASSERT(UNREACHED); }
void LockSbbH() { ASSERT(UNREACHED); }
void LockSbbW() { ASSERT(UNREACHED); }

// SUB
void LockSubB() { ASSERT(UNREACHED); }
void LockSubH() { ASSERT(UNREACHED); }
void LockSubW() { ASSERT(UNREACHED); }

// XOR
void LockXorB() { ASSERT(UNREACHED); }
void LockXorH() { ASSERT(UNREACHED); }
void LockXorW() { ASSERT(UNREACHED); }

#endif // CFG_NATIVE_PROCESS
